package org.dxl.server;

import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.dxl.opt.WindowMain;
import org.dxl.result.AjaxResult;
import org.dxl.util.IoCloseUtil;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * 响应，目前响应支持回写字符串数据，也就是你可以将数据转成json串再回写，也可直接回写你想返回的字符串
 *
 * @author Administrator
 */
@Slf4j
public class Response {
    /**
     * 响应头字符串最终写回信息
     */
    private StringBuilder headInfo;

    /**
     * 设置的响应头信息，在servlet接口中我们还可以对这个进行修改的
     */
    private Map<String, String> headInfos;

    /**
     * 响应内容，最终写回信息
     */
    private StringBuilder content;
    /**
     * 响应内容的长度
     */
    private int length;
    /**
     * 流
     */
    private BufferedWriter bw;

    /**
     * 换行
     */
    private static final String CRLF = "\r\n";
    /**
     * 空格
     */
    private static final String BLANK = " ";

    /**
     * 构造方法
     */
    private Response() {
        headInfo = new StringBuilder();
        content = new StringBuilder();
        headInfos = new LinkedHashMap<>();
    }

    /**
     * 带参构造方法
     *
     * @param os OutputStream
     */
    public Response(OutputStream os) {
        this();//调用本类的无参构造方法
        try {
            bw = new BufferedWriter(new OutputStreamWriter(os, StandardCharsets.UTF_8));
            // 添加支持跨域请求
            this.acceptCors();
        } catch (RuntimeException e) {
            log.error("构造response输出流出错");
            log.error(e.getMessage(), e);
            WindowMain.addInfo("构造response输出流出错：" + e.getMessage());
            headInfo = null;
        }
    }

    /**
     * 构造正文部分,不带换行的写入
     *
     * @param info 信息
     * @return Response
     */
    public Response print(String info) {
        content.append(info);
        try {
            length += info.getBytes(StandardCharsets.UTF_8).length;
            // 修改响应体Content-Length的参数值为最新值,响应返回内容变了，所以一定要实时修改该值的长度值，否则客户端无法接收到响应
            this.updateContentLengthForHeader();
        } catch (RuntimeException e) {
            log.error("构造response输出信息出错");
            log.error(e.getMessage(), e);
            WindowMain.addInfo("构造response输出信息出错：" + e.getMessage());
        }
        return this;
    }

    /**
     * 构造正文部分，带换行符的写入
     *
     * @param info 信息
     * @return Response
     */
    public Response println(String info) {
        content.append(info).append(CRLF);
        try {
            length += (info + CRLF).getBytes(StandardCharsets.UTF_8).length;
            // 修改响应体Content-Length的参数值为最新值,响应返回内容变了，所以一定要实时修改该值的长度值，否则客户端无法接收到响应
            this.updateContentLengthForHeader();
        } catch (RuntimeException e) {
            log.error("构造response输出信息出错");
            log.error(e.getMessage(), e);
            WindowMain.addInfo("构造response输出信息出错：" + e.getMessage());
        }
        return this;
    }

    /**
     * 构造响应头
     *
     * @param code 错误码
     */
    private void createHeadInfo(int code) {
        log.info("开始创建响应头信息。。。。");
        WindowMain.addInfo("开始创建响应头信息。。。。");
        headInfo.append("HTTP/1.1").append(BLANK).append(code).append(BLANK);
        switch (code) {
            case 200:
                headInfo.append("OK");
                break;
            case 500:
                headInfo.append("SERVER ERROR");
                break;
            default:
                headInfo.append("NOT FOUND");
                break;
        }
        headInfo.append(CRLF);
        // 修改响应体Content-Length的参数值为最新值,响应返回内容变了，所以一定要实时修改该值的长度值，否则客户端无法接收到响应
        // 其实在println和print方法中，也就是添加返回内容的方法中，已经调用了该方法，这里是可以不加的，
        // 但是，这里加上的好处是：避免因为增加添加返回信息新的方法后，而忘记了添加这个刷新响应头数据的方法，这里是做一个兜底的作用，也就是能够保证
        // 返回信息一定能够被接收到
        this.updateContentLengthForHeader();
        // 统一将设置好的响应头信息写入要传输到客户端的字符串中，这里统一写入的好处是在接口中可以对响应头进行设置修改
        this.uniteAddAllSettingHeader();
        headInfo.append(CRLF);
        log.info("响应头信息：" + CRLF + headInfo.toString());
        WindowMain.addInfo("响应头信息：" + CRLF + headInfo.toString());
        log.info("结束创建响应头信息。。。。");
        WindowMain.addInfo("结束创建响应头信息。。。。");
    }

    /**
     * 添加最基本的响应信息
     */
    private void addBaseResponseHeader() {
        // 说明一下，即使前端指定为返回是json格式数据，这里也可不用更改类型，只需要调用print方法返回的是json字符串，前端会自动解析json串为对象
        this.setResponseHeader("Content-Type", "text/html;charset=utf-8");
        this.setResponseHeader("Content-Length", String.valueOf(length));
    }

    /**
     * 修改响应体Content-Length的参数值为最新值
     */
    private void updateContentLengthForHeader(){
        this.setResponseHeader("Content-Length", String.valueOf(length));
    }

    /**
     * 支持跨域请求的响应头信息
     */
    private void acceptCors() {
        this.setResponseHeader("Access-Control-Allow-Origin", "*");
        //表示的是跨域的ajax中可以携带cookie，此时第一项设置不能为*，需指定域名；
        this.setResponseHeader("Access-Control-Allow-Credentials", "true");
        //表示的是允许跨域的请求方法；
        this.setResponseHeader("Access-Control-Allow-Methods", "GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, PATCH");
        //表示的是允许跨域请求包含content-type头；
        this.setResponseHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type,Authorization,Token,Accept, Connection, User-Agent, Cookie");
        //表示的是在3628800秒内，不需要再发送预检验请求，可以缓存该结果，一般默认。
        this.setResponseHeader("Access-Control-Max-Age", "3628800");
    }

    /**
     * 设置响应头信息
     *
     * @param key   键值
     * @param value value值
     */
    public void setResponseHeader(String key, String value) {
        if (StringUtils.isNotBlank(key)) {
            if (StringUtils.isBlank(value)) {
                value = "";
            }
            this.headInfos.put(key, value);
        }
    }

    /**
     * 统一追加响应头信息
     */
    private void uniteAddAllSettingHeader() {
        if (CollectionUtil.isNotEmpty(headInfos)) {
            for (String key : headInfos.keySet()) {
                String value = headInfos.get(key);
                this.setResponseHeaderByString(key + ":" + value);
            }
        }
    }

    private void setResponseHeaderByString(String line) {
        if (StringUtils.isNotBlank(line)) {
            headInfo.append(line).append(CRLF);
        }
    }

    /**
     * 获取响应头信息
     *
     * @param key 响应头键值
     * @return 响应头指定键value值
     */
    public String getHeader(String key) {
        return this.headInfos.get(key);
    }

    /**
     * 推送到客户机的浏览器
     *
     * @param code 错误码
     */
    public void pushToClient(int code) {
        if (headInfo == null) {
            code = 500;
        }
        try {
            //调用本类中的构造响应头
            this.createHeadInfo(code);
            bw.write(headInfo.toString());
            bw.write(content.toString());
            bw.flush();
        } catch (IOException e) {
            log.error("向客户端回写信息出错");
            log.error(e.getMessage(), e);
            WindowMain.addInfo("向客户端回写信息出错：" + e.getMessage());
        } finally {
            this.close();
        }
    }

    public void close() {
        IoCloseUtil.closeAll(bw);
    }

    /**
     * 测试
     *
     * @param args 参数
     */
    public static void main(String[] args) {
        // 所有new出的本类对象的私有属性对本类的任何静态方法都是可见的
        Response response = new Response(new ByteArrayOutputStream());
        Map<String, String> headInfos = response.headInfos;

        // 打印响应头
        System.out.println("一：");
        System.out.println(headInfos);

        response.setResponseHeader("Content-Type", "text/json;charset=utf-8");

        // 打印响应头
        System.out.println("二：");
        System.out.println(headInfos);

        response.println(JSON.toJSONString(AjaxResult.success("操作成功")));
        response.pushToClient(200);

        // 打印响应头字符串
        System.out.println("三：");
        System.out.println(response.headInfo);
        System.out.println(response.content);
    }
}
